package com.serena.da.rest.services.deploy;

import com.serena.da.rest.common.GlobalContext;
import com.serena.da.rest.http_helpers.Response;
import com.serena.da.rest.common.Utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Application Service
 *
 * @author Spitkovskyi Igor
 */
public class ApplicationService extends SraDeployService {
    private static ApplicationService applicationService;

    private final String METHOD_APPLICATION = "application/";
    private final String METHOD_APPLICATION_PROCESS = "applicationProcess/";

    private ApplicationService() throws Exception {
        super();
    }

    public static ApplicationService getInstance() throws Exception {
        if (applicationService == null) {
            applicationService = new ApplicationService();
        }
        return applicationService;
    }

    private String getRestUrlApplication() {
        return getRestUrl(METHOD_APPLICATION);
    }

    private String getRestUrlApplicationProcess() {
        return getRestUrl(METHOD_APPLICATION_PROCESS);
    }

    /**
     * Gets all applications existing in SRA server.
     */
    public Response getApplications() throws Exception {
        return getApplications(false);
    }

    public Response getApplications(boolean useUrlAuthentication) throws Exception {
        String methodParameter = "all";
        String restCallUrl = getRestUrl(METHOD_APPLICATION + methodParameter, useUrlAuthentication);

        return executeRequest(restCallUrl);
    }

    /**
     * Create Application
     */
    public Response createApplication(String name, String description, String notificationSchemeId, String enforceCompleteSnapshots) throws Exception {
        HashMap<String, String> hashMap = new HashMap<>();
        hashMap.put("name", name);
        hashMap.put("description", description);
        hashMap.put("notificationSchemeId", notificationSchemeId);
        hashMap.put("enforceCompleteSnapshots", enforceCompleteSnapshots);

        String jsonString = Utils.replaceJsonTemplateKeys("Application.js", hashMap);

        Response response = executeRequest(getRestUrlApplication(), Method.PUT, jsonString, GlobalContext.APP_PREFIX);
        printReturnedInformation("Application", response.getResponseBody());

        return response;
    }

    //CREATE APPLICATION PROCESS
    public boolean createApplicationProcess(String ParentApplicationName, String ProcName, String ProcDescr) throws Exception {
        Response response;
        boolean creationSuccess = false;
        String AppID = GlobalContext.getApplicationID(ParentApplicationName);   //Get(APP_PREFIX + ParentApplicationName).toString();
        String url = getRestUrlApplicationProcess();

        //PutRequest putReq = new PutRequest(url);

        try {
            String json = getJSON_CreateApplicationProcess(AppID, ProcName, ProcDescr, "", "");

            /**
             * usage example.
             * You can also wrap replaceJsonTemplateKeys method with your
             * getJSON_CreateApplicationProcess performing validations for empty string
             * if you find it is a good idea.
             *
             * you can use @Optional("AUTOMATIC") parameters instead :)
             *
             * PLEASE DELETE THIS COMMENT :)
             */
            HashMap<String, String> hashMap = new HashMap<>();
            hashMap.put("applicationId", AppID);
            hashMap.put("appProcName", ProcName);
            hashMap.put("appProcDescr", ProcDescr);
            hashMap.put("inventoryManagementType", "AUTOMATIC");
            hashMap.put("offlineAgentHandling", "PRE_EXECUTION_CHECK");

            //String json2 = Utils.replaceJsonTemplateKeys("CreateGlobalEnvironment.js", hashMap);

            response = executeRequest(url, Method.PUT, json, GlobalContext.APP_PROCESS_PREFIX);
            creationSuccess = response.getSuccess();
            if (creationSuccess) {
                printReturnedInformation("Application Process", response.getResponseBody());
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return creationSuccess;
    }

    //APPLICATION ADDS COMPONENT
    public boolean addComponents(String ApplicationName, String[] ComponentNames) throws Exception {
        for (int i = 0; i < ComponentNames.length; i++) {
            try {
                addComponent(ApplicationName, ComponentNames[i]);
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
        return true;
    }

    public boolean addComponent(String ApplicationName, String ComponentName) throws Exception {
        //http : //localhost:8081/serena_ra/rest/deploy/application/e984650c-f209-491e-a009-38941c32c5bb/addComponents
        Response response;
        boolean creationSuccess = false;
        String ADD_COMPONENT_ACTION = "addComponents";
        String url = getRestUrlApplication();
        String applicationID = GlobalContext.getApplicationID(ApplicationName);   //Get(APP_PREFIX + ApplicationName).toString();
        String componentID = GlobalContext.getComponentID(ComponentName);   //Get(COMPT_PREFIX + ComponentName).toString();

        if (componentID.isEmpty()) {
            throw new Exception("Could not find component by name specified.");
        }

        url = url + applicationID + "/" + ADD_COMPONENT_ACTION;
        //PutRequest putReq = new PutRequest(url);

        String json = getJSON_ApplicationAddComponent(componentID);
        try {
            response = executeRequest(url, Method.PUT, json);
            System.out.print("Component added to Application");
        } catch (Exception e) {
            e.printStackTrace();
            return creationSuccess;
        }

        return response.getSuccess();
    }

    public boolean createApplicationProcessWorkflow(String ApplicationName, String ApplicationProcessName, LinkedHashMap<String, AppProcessStep> workflowSteps) throws Exception {
        //http://localhost:8081/serena_ra/rest/deploy/applicationProcess/84b9d343-9c0f-435e-bbf8-7516c9103cb8/saveActivities
        String SAVE_WORKFLOW_ACTION = "/saveActivities";
        Response response;
        boolean creationSuccess = false;

        String workflowName = Utils.getMD5(ApplicationProcessName + Utils.getTimestamp());
        String ApplicationID = GlobalContext.getApplicationID(ApplicationName);
        String ApplicationProcessID = GlobalContext.GetApplicationProcessID(ApplicationProcessName);

        String url = getRestUrlApplicationProcess() + ApplicationProcessID + SAVE_WORKFLOW_ACTION;

        //PutRequest req = new PutRequest(url);
        try {
            //Determine current version count
            int workflowVersionsCount = getApplicationWorkflowVersionsCount(ApplicationID, ApplicationProcessID); //DONT close connection on complete
            //req.setHeader(new BasicHeader("applicationProcessVersion", "" + (workflowVersionsCount)));  //IMPROVE: Where should it be?

            String json = getJSON_CreateApplicationProcessMultistepWorkflow(workflowName, workflowSteps);

            //putReq.setHeader(new BasicHeader("componentProcessVersion", "" + (workflowVersionsCount)));  //IMPROVE: Where should it be?

            //response = executeRequest(req, json, "Application process workflow: ");
            HashMap<String, String> headers = new HashMap<String, String>();
            headers.put("applicationProcessVersion", "" + (workflowVersionsCount));
            response = executeRequest(url, Method.PUT, json, headers);
            creationSuccess = response.getSuccess();
            if (creationSuccess) {
                printReturnedInformation("Application process workflow: ", response.getResponseBody());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return creationSuccess;
    }

    //GET NUMBER OF EXISTING WORKFLOWS IN SPECIFIED COMPONENT PROCESS
    private int getApplicationWorkflowVersionsCount(String ApplicationID, String ApplicationProcessID) throws Exception {
        String APPLICATION_PROCESSES_WITH_VERSIONS = "/fullProcesses";

        //  http://localhost:8081/serena_ra/rest/deploy/component/09c35624-376b-4377-bd56-521d7e26dff5/fullProcesses/active
        String url = getRestUrlApplication() + ApplicationID + APPLICATION_PROCESSES_WITH_VERSIONS;
        return getWorkflowVersionsCount(url, ApplicationProcessID);
    }

    ///////////////////////////////////////////////////////////////////////////////////////////
    ///////////////////////      JSONS     //////////////////////////////
    ///////////////////////////////////////////////////////////////////////////////////////////
    private String getJSON_CreateApplication(String name, String description, String NotificationScheme, String EnforceCompleteSnapshots) {
        String notification_sch;
        if (NotificationScheme.equalsIgnoreCase("Default Notification Scheme")) {
            notification_sch = "00000000-0000-0000-0000-000000000000";
        } else {
            notification_sch = "";
        }
        return "{" +
                "\"name\" : \"" + name + "\"," +
                "\"description\" : \"" + description + "\"," +
                "\"notificationSchemeId\" : \"" + notification_sch + "\"," +
                "\"enforceCompleteSnapshots\" : \"" + EnforceCompleteSnapshots + "\"" +
                "}";
    }

    //!!! ALSO ADD IMPLEMENTATION WHEN ARRAY OF COMPONENTS PASSED !!!
    private String getJSON_ApplicationAddComponent(String ComponentID) {
        return "{" +
                "\"components\" : [\"" + ComponentID + "\"]" +      //cc57c0b0-f887-4be8-bd3d-f1a0329aa0b2
                "}";
    }

    private String getJSON_CreateApplicationProcess(String AppID, String AppProcName, String AppProcDescr, String AppProcType,
                                                    String AppProcAgentHandling) {
        String type;
        String agentHandling;

        HashMap<String, String> processTypes = new HashMap<String, String>() {
            {
                put("advanced", "ADVANCED");
                put("automatic", "AUTOMATIC");
            }
        };
        HashMap<String, String> agentHandlingTypes = new HashMap<String, String>() {
            {
                put("check before execution", "PRE_EXECUTION_CHECK");
                put("use all available; report failure", "FAIL_BUT_CONTINUE");
                put("always report success", "ALLOW_OFFLINE");
            }
        };


        if (AppProcType.equalsIgnoreCase("advanced") || AppProcType.equalsIgnoreCase("automatic")) {
            type = processTypes.get(AppProcType.toLowerCase());
        } else {
            type = processTypes.get("automatic");
        }

        if (AppProcAgentHandling.equalsIgnoreCase("check before execution") || AppProcAgentHandling.equalsIgnoreCase("use all available; report failure") || AppProcAgentHandling.equalsIgnoreCase("always report success")) {
            agentHandling = agentHandlingTypes.get(AppProcAgentHandling);
        } else {
            agentHandling = "PRE_EXECUTION_CHECK";
        }

        return "{" +
                "\"name\" : \"" + AppProcName + "\"," +
                "\"description\" : \"" + AppProcDescr + "\"," +
                "\"inventoryManagementType\" : \"" + type + "\"," +
                "\"offlineAgentHandling\" : \"" + agentHandling + "\"," +
                "\"applicationId\" : \"" + AppID + "\"," +
                "\"versionPresets\" : []" +         //SHOULD NOT BE HARDCODED. TEMPORARY IMPLEMENTATION
                "}";
    }


    private String getJSON_CreateApplicationProcessMultistepWorkflow(String workflowName, LinkedHashMap<String, AppProcessStep> workflowSteps) throws Exception {
        String json;
        String workflowStart = "{" +
                "\"type\" : \"graph\",";
        String workflowChildrenHeader = "\"children\" : [";

        String stepBegin = "{";

        String workflowFirstStep = "\"type\" : \"finish\"," +
                "\"name\" : \"" + workflowName + "\"";

        String stepEnd = "}";

        String workflowChildrenFooter = "], ";

        String workflowFooter = "}";

        ArrayList<String> stepNames = new ArrayList<String>();

        if (workflowSteps.isEmpty()) {
            throw new Exception("No steps / components / component processes provided.");
        }

        json = workflowStart + workflowChildrenHeader + stepBegin + workflowFirstStep + stepEnd + ",";

        int numberOfSteps = workflowSteps.size();
        if (numberOfSteps > 0) {
            int counter = 1;

            for (Map.Entry<String, AppProcessStep> entry : workflowSteps.entrySet()) {
                String StepName = entry.getKey();
                String StepNameMD5 = Utils.getMD5(StepName);
                String ComponentName = entry.getValue().ComponentName;
                String ComponentProcessName = entry.getValue().ComponentProcessName;
                String StepType = entry.getValue().StepType;

                stepNames.add(StepNameMD5);

                json = json + stepBegin;
                json = json + getHeaderForStep(StepNameMD5, ComponentName);
                json = json + getComponentForStep(ComponentName) + ", ";
                json = json + getParametersForStep(StepName, StepType, ComponentName, ComponentProcessName);
                json = json + stepEnd;

                if (counter < numberOfSteps) {
                    json = json + ", ";
                }
                counter++;
            }
        }

        String[] step_names = new String[stepNames.size()];
        stepNames.toArray(step_names);
        json = json + workflowChildrenFooter;
        json = json + getWorkflowGeometrySection(step_names, workflowName);
        json = json + workflowFooter;
        return json;
    }

    private String getHeaderForStep(String name, String componentName) {
        return "\"name\" : \"" + name + "\"," +   //ca1185472a6487a7b3cea841ec4050
                "\"type\" : \"componentEnvironmentIterator\"," +  //This value is identical for InstallComponent, UninstallComponent, Rollback and RunForEachVersion step types. Leave it hardcoded so far.
                "\"componentName\" : \"" + componentName + "\"," +   //isAutoCompFS_a
                "\"component\" : ";
    }

    private String getComponentForStep(String ComponentName) throws Exception {
        //http://localhost:8081/serena_ra/rest/deploy/component/9a3e831e-e988-41ff-9b5d-a93ad9d2c44f
        String ComponentInfo;
        String ComponentID = GlobalContext.getComponentID(ComponentName);
        //ComponentService c_service = new ComponentService(host, port, user, pwd);
        ComponentService c_service = ComponentService.getInstance();
        try {
            ComponentInfo = c_service.getComponent(ComponentID);
        } catch (Exception e) {
            return null;
        }

        return ComponentInfo;
    }

    private String getParametersForStep(String StepName, String StepType, String ComponentName, String ComponentProcessName) {
        return getParametersForStep(StepName, StepType, ComponentName, ComponentProcessName, "false", "-1", "false", "");
    }

    private String getParametersForStep(String StepName, String StepType, String ComponentName, String ComponentProcessName, String RunOnFirstOnline, String MaxIteration, String FailFast, String PreconditionScript) {
        String stepMD5Name = "default";
        try {
            stepMD5Name = Utils.getMD5(StepType + ComponentName + ComponentProcessName + Utils.getTimestamp());
        } catch (Exception e) {
            System.out.println("Could not generate MD5 for application process name.");
        }

        String stepType;

        //Following code is shity.
        if (StepType.equalsIgnoreCase("install component")) {
            stepType = "inventoryVersionDiff";
        } else if (StepType.equalsIgnoreCase("uninstall component")) {
            stepType = "uninstallAllVersionsIterator";
        } else if (StepType.toLowerCase().contains("rollback")) {
            stepType = "uninstallVersionDiff";
        } else if (StepType.toLowerCase().contains("run process") && StepType.toLowerCase().contains("each version")) {
            stepType = "allVersionsIterator";
        } else {
            stepType = "inventoryVersionDiff";
        }

        //Following code is shity. Need to parameterize these parameters also
        String TypeSpecificParameters = "";
        if (stepType == "inventoryVersionDiff") {
            TypeSpecificParameters = TypeSpecificParameters +
                    "\"status\" : \"Active\",";
        } else if (stepType == "uninstallAllVersionsIterator") {
            TypeSpecificParameters = TypeSpecificParameters +
                    "\"selectionType\" : \"ALL_EXISTING\"," +
                    "\"status\" : \"Active\",";
        } else if (stepType == "uninstallVersionDiff") {
            TypeSpecificParameters = TypeSpecificParameters +
                    "\"selectionType\" : \"UNDESIRED\"," +
                    "\"status\" : \"Active\",";
        }

        return "\"runOnlyOnFirst\" : \"" + RunOnFirstOnline + "\"," + //false
                "\"maxIteration\" : \"" + MaxIteration + "\"," + //-1
                "\"failFast\" : \"" + FailFast + "\"," +    //false
                "\"preconditionScript\" : \"" + PreconditionScript + "\"," +
                "\"children\" : [{" +
                "\"name\" : \"" + stepMD5Name + "\"," +        //b792f4b014ca39287cfff61056c715
                "\"type\" : \"" + stepType + "\"," +            //allVersionsIterator
                "\"componentName\" : \"" + ComponentName + "\"," +      //isAutoCompFS_a
                TypeSpecificParameters +
                "\"children\" : [{" +
                "\"name\" : \"" + StepName + "\"," +
                "\"type\" : \"componentProcess\"," +        //CONSTANT
                "\"componentName\" : \"" + ComponentName + "\"," +  //isAutoCompFS_a
                "\"componentProcessName\" : \"" + ComponentProcessName + "\"," +    //isAutoCompFS_a
                "\"allowFailure\" : \"false\"," +
                "\"properties\" : {}" +                 //CONSTANT FOR NOW. HAVEN'T IMPLEMENTED PROPERTIES.
                "}" +
                "]" +
                "}" +
                "]";
    }
}
